package org.hackystat.iw.projectoverviewer.page.treemap;

/*
 * This code is highly inspired by Object Lab.
 * 
 * www.ObjectLab.co.uk
 */

import java.awt.Color;
import java.awt.Graphics;
import java.util.Enumeration;
import javax.swing.JPanel;
import net.sf.jtreemap.swing.ColorProvider;
import net.sf.jtreemap.swing.DefaultValue;
import net.sf.jtreemap.swing.JTreeMap;
import net.sf.jtreemap.swing.TreeMapNode;
import net.sf.jtreemap.swing.Value;

/**
 * An HSB color space color provider for JTreeMap. Uses a specified function to map the values onto
 * the HSB color space. The default is a linear function, but in my experience one of the
 * logarithmic ones works best for this color space.
 * 
 * @author Andy Adamczak
 * @author Nathan Britton
 */
public class RedYellowGreenColorProvider extends ColorProvider {

  /** Support serialization. */
  private static final long serialVersionUID = 1L;

  /**
   * Enumeration of different color distribution types.
   * 
   * @author Andy Adamczak
   */
  public enum ColorDistributionTypes {
    /** Linear grow.*/
    Linear, 
    /** Logarithmic grow. */
    Log, 
    /** Exponential grow. */
    Exp, 
    /** Square root grow. */
    SquareRoot, 
    /** Cubic root grow. */
    CubicRoot
  }

  /**
   * Constructor.
   * 
   * @param treeMap The JTreeMap instance.
   */
  public RedYellowGreenColorProvider(final JTreeMap treeMap) {
    this(treeMap, ColorDistributionTypes.Linear);
  }

  /**
   * Constructor.
   * 
   * @param treeMap The JTreeMap instance.
   * @param colorDistribution The type of color distribution.
   */
  public RedYellowGreenColorProvider(final JTreeMap treeMap,
      final ColorDistributionTypes colorDistribution) {

    super();
    jTreeMap = treeMap;
    this.colorDistribution = colorDistribution;
  }

  /**
   * Returns the legend panel.
   * 
   * @return The legend penel.
   */
  @Override
  public JPanel getLegendPanel() {
    if (legend == null) {
      legend = new Legend();
    }

    return legend;
  }

  /**
   * Returns the colors.
   * 
   * @param value The value instance.
   * @return The colors.
   */
  @Override
  public Color getColor(final Value value) {
    // Figure out the current range of colors, map that range into a scale
    // from 0 to 1,
    // using the specified distribution type
    if (maxValue == null || minValue == null) {
      setValues(jTreeMap.getRoot());
    }
    final double max = this.maxValue.getValue();
    // Use -1 if no value exists for this entry
    double val;
    if (value == null ) {
      val = -1;
    }
    else {
      val = value.getValue();
    }

    if (val < 0) {
      // If value is below 0, display black
      return Color.getHSBColor(0, 0, 0);
    }
    else {
      // If value is positive, then calculate it's position between min and max
      double ratio = val / max;
      //Apply mathematical options
      ratio = adjustValue(ratio);
      // Then multiply this ratio by 0.35 to get the appropriate hue between red and green
      float newHue = (float) ratio * 0.35f;
      // The hue is multiplied by 360 by getHSBColor to the the appropriate hue
      // so we use 0.35 because green is 120.

      return Color.getHSBColor(newHue, 1f, 1f);
    }
  }

  /**
   * Given a value, maps that value to a new value using the specified math function
   * 
   * 
   * @param value The value to convert
   * @return The converted value
   */
  private double adjustValue(final double value) {
    double ret = value;
    switch (colorDistribution) {
    case Log:
      ret = Math.log1p(value);
      break;
    case Exp:
      ret = Math.exp(value);
      break;
    case SquareRoot:
      ret = Math.sqrt(value);
      break;
    case CubicRoot:
      ret = Math.cbrt(value);
      break;
    default:
      // Linear
      ret = value;
      break;
    }
    return ret;
  }

  /**
   * Set the max and the min values in the tree map
   * 
   * @param root Root of the JTreeMap
   */
  private void setValues(final TreeMapNode root) {
    if (root.isLeaf()) {
      final Value value = root.getValue();

      if (value == null) {
        return;
      }

      if (maxValue == null || value.getValue() >= maxValue.getValue()) {
        try {
          final Class<? extends Value> c = value.getClass();
          if (maxValue == null) {
            maxValue = (c.newInstance());
          }
          maxValue.setValue(value.getValue());
        }
        catch (final IllegalAccessException iae) {
          iae.printStackTrace();
        }
        catch (final InstantiationException ie) {
          ie.printStackTrace();
        }
      }

      minValue = new DefaultValue(0);

    }
    else {
      for (final Enumeration<?> e = root.children(); e.hasMoreElements();) {
        final TreeMapNode node = (TreeMapNode) e.nextElement();
        setValues(node);
      }
    }
  }

  private JTreeMap jTreeMap;

  private JPanel legend;

  private Value maxValue;

  private Value minValue;

  private ColorDistributionTypes colorDistribution = ColorDistributionTypes.Linear;

  /**
   * Panel with the legend
   * 
   * @author Laurent Dutheil
   */
  private class Legend extends JPanel {
    private static final int Y_INSET = 7;

    private static final int X_INSET = 15;

    private static final long serialVersionUID = 6371342387871103592L;

    private static final int HEIGHT = 20;

    private static final int WIDTH = 120;

    private static final int X = 20;

    private static final int Y = 25;

    /**
     * Constructor of Legend
     */
    public Legend() {
      this.setSize(
          new java.awt.Dimension(2 * Legend.X + Legend.WIDTH, 2 * Legend.Y + Legend.HEIGHT));
      this.setPreferredSize(
          new java.awt.Dimension(2 * Legend.X + Legend.WIDTH, 2 * Legend.Y + Legend.HEIGHT));
    }

    /**
     * Draw the legend component.
     * 
     * @param g The graphics instance.
     */
    @Override
    public void paintComponent(final Graphics g) {
      super.paintComponent(g);
      if (RedYellowGreenColorProvider.this.minValue == null
          || RedYellowGreenColorProvider.this.maxValue == null) {
        setValues(RedYellowGreenColorProvider.this.jTreeMap.getRoot());
      }
      final Value min = RedYellowGreenColorProvider.this.minValue;
      final Value max = RedYellowGreenColorProvider.this.maxValue;

      g.setColor(Color.black);
      if (min != null && max != null) {
        g.drawString(min.getLabel(), Legend.X - X_INSET, Legend.Y - Y_INSET);
        g.drawString(max.getLabel(), Legend.X + Legend.WIDTH - X_INSET, Legend.Y - Y_INSET);

        final double step = (max.getValue() - min.getValue()) / Legend.WIDTH;
        final Value value = new DefaultValue(min.getValue());
        for (int i = 0; i < Legend.WIDTH; i++) {
          g.setColor(RedYellowGreenColorProvider.this.getColor(value));
          g.fillRect(Legend.X + i, Legend.Y, 1, Legend.HEIGHT);
          value.setValue(value.getValue() + step);
        }
      }
    }
  }
}
